package com.hevery.beans.xml;

import com.hevery.beans.BeanReference;
import com.hevery.beans.AbstractBeanDefinitionReader;
import com.hevery.beans.BeanDefinition;
import com.hevery.beans.PropertyValue;
import com.hevery.beans.io.ResourceLoader;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.InputStream;

/**
 * @description: resource为xml格式的beanDefinition的读取器
 * @author: hongyu.zhang
 * @create: 2018-03-21
 **/
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

    public XmlBeanDefinitionReader(ResourceLoader resourceLoader) {
        super(resourceLoader);
    }
    /**
     * 加载并通过BeanDefinition注册Bean
     * @param location
     * @throws Exception
     */
    @Override
    public void loadBeanDefinitions(String location) throws Exception {
        //获取资源输入流
        InputStream inputStream = super.getResourceLoader().getResource(location).getInputStream();
        //从xml文件中读取所有bean的定义，并注册到registry中
        //注意: 此时bean定义里并没有实例化该bean
        doLoadBeanDefinition(inputStream);
    }

    /**
     * 读取输入流到doc对象
     * @param inputStream
     */
    private void doLoadBeanDefinition(InputStream inputStream) throws Exception{
        //获取工厂
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        //获取生成器
        DocumentBuilder builder = factory.newDocumentBuilder();
        //解析为Document
        Document document = builder.parse(inputStream);
        //解析并注册bean
        registerBeanDefinition(document);
        inputStream.close();
    }

    /**
     * 解析并注册bean
     * @param document
     */
    private void registerBeanDefinition(Document document) {
        //通过document获取根标签 <beans><beans/>
        Element root = document.getDocumentElement();
        parseBeanDefinition(root);
    }

    /**
     * 从根对象开始遍历dom树(root---><beans><beans/>)
     * @param root
     */
    private void parseBeanDefinition(Element root){
        //获取所有的 <bean><bean/>......
        NodeList nl = root.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if(node instanceof Element){
                Element ele = (Element)node;
                //解析<bean>标签
                processBeanDefinition(ele);
            }

        }
    }

    /**
     * 解析每个节点的id和class,并将其设置到BeanDefinition中
     * <bean id="helloWorldService" class="com.hevery.service.HelloWorldServiceImpl"><beans/>
     * @param ele
     */
    private void processBeanDefinition(Element ele) {
        //获取bean标签的id属性作为bean的name
        String name = ele.getAttribute("id");
        //获取bean标签的class作为bean的className
        String className = ele.getAttribute("class");
        BeanDefinition beanDefinition = new BeanDefinition();
        //解析<bean>标签下的子标签<property>
        processProperty(ele,beanDefinition);
        // 设置className的同时，也在内部设置了Class
        beanDefinition.setBeanClassName(className);
        //往hashMap中写入 id 和 beanDefinition -------> 实现map的写入
        getRegistry().put(name,beanDefinition);
    }

    /**
     * 将解析节点中的数据设置到BeanDefinition中(向BeanDefinition中)
     * <property name="text" value="Hello World!"></property>
     * @param ele
     * @param beanDefinition
     */
    private void processProperty(Element ele, BeanDefinition beanDefinition) {
        NodeList propertyNode = ele.getElementsByTagName("property");
        for (int i = 0; i < propertyNode.getLength(); i++) {
            Node node = propertyNode.item(i);
            if(node instanceof Element){
                Element propertyEle = (Element)node;
                String name = propertyEle.getAttribute("name");
                String value = propertyEle.getAttribute("value");
                // 如果是value型的属性值
                if(value != null && value.length() > 0){
                    beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name,value));
                }else {
                    //否则是ref型的属性值
                    String ref = propertyEle.getAttribute("ref");
                    if(ref == null || ref.length() == 0){
                        throw new IllegalArgumentException("Configuration problem: <property> element for property '"
                                + name + "' must specify a ref or value");
                    }
                    BeanReference beanReference = new BeanReference(ref);
                    //保存一个ref型属性值的属性
                    beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name,beanReference));

                }
            }
        }
    }
}
